feat(push): add final status block and structured JSON output#1199
feat(push): add final status block and structured JSON output#1199l2ysho wants to merge 5 commits into
Conversation
Rework the end of `apify push` so agents can distinguish source-upload success from final cloud-build success (#1136). - `apify push` prints a final result block (result, upload, build, IDs, URLs, exit code, and a failure reason log tail). - `apify push --json` returns a structured result object with `ok`, `operation`, `actor`, `build`, optional `error`, and `exitCode`. - Failed, timed-out, and aborted builds return non-zero exit codes; `ok` mirrors command success. Closes #1136 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…son-output-for-agents2
…son-output-for-agents2
| // How many trailing log lines to surface as the failure reason. | ||
| const BUILD_LOG_TAIL_LINES = 10; | ||
|
|
||
| interface PushResult { |
There was a problem hiding this comment.
Nit: prefer type over interface.
There was a problem hiding this comment.
for some reason we use 'typescript/consistent-type-definitions': ['error', 'interface'], (even before oxlint migration). We use @apify/oxlint-config but there is few additional changes in it, I guess lets align this with Apify Coding Standards in separate PR (too much noise to change all interfaces to types here).
@vladfrangu do you have some context about this type/interface decision ?
| build: { id: string; number: string; status: string; url: string }; | ||
| error?: { phase: 'build'; message: string; logTail: string[] }; | ||
| exitCode: number; | ||
| } |
There was a problem hiding this comment.
Nit: to avoid type-checking for optional error, we could leverage discriminated union here:
type PushResultBase = {
operation: 'push';
actor: { id: string; url: string };
build: { id: string; number: string; status: string; url: string };
exitCode: number;
};
type PushSuccess = PushResultBase & {
ok: true;
};
type PushFailure = PushResultBase & {
ok: false;
error: { phase: 'build'; message: string; logTail: string[] };
};
type PushResult = PushSuccess | PushFailure;
| case ACTOR_JOB_STATUSES.READY: | ||
| case ACTOR_JOB_STATUSES.RUNNING: | ||
| return { resultLabel: 'RUNNING', exitCode: 0, ok: true }; |
There was a problem hiding this comment.
Q: might be a bit misleading. Why don't we have resultLabel: 'READY'?
There was a problem hiding this comment.
As I thinking about this now, maybe it would be better to READY and RUNNING status resolve to IN_PROGRESS result label? as READY + ok: true feels like DONE at first glance but actually it is waiting to be processed by worker
| exitCode: number; | ||
| ok: boolean; | ||
| errorMessage?: string; | ||
| } { |
There was a problem hiding this comment.
Nit: the return type would deserve to be extracted into its own type.
| exitCode: CommandExitCodes.BuildTimedOut, | ||
| ok: false, | ||
| errorMessage: 'Build timed out', | ||
| }; |
There was a problem hiding this comment.
Q: similarly as above, why don't we have distinguished resultLabel + errorMessage for both ABORTED and ABORTING (respectively TIMED_OUT and TIMING_OUT)?
| return { resultLabel: 'SUCCEEDED', exitCode: 0, ok: true }; | ||
| case ACTOR_JOB_STATUSES.READY: | ||
| case ACTOR_JOB_STATUSES.RUNNING: | ||
| return { resultLabel: 'RUNNING', exitCode: 0, ok: true }; |
There was a problem hiding this comment.
Nit: maybe the exitCode should be optional and not returned in case the process hasn't ended yet. Technically, at a time of READY and RUNNING, there is no exitCode yet. ok is alright, as at that time the process is truly ok.
| errorMessage: 'Build timed out', | ||
| }; | ||
| default: | ||
| return { resultLabel: 'FAILED', exitCode: CommandExitCodes.BuildFailed, ok: false, errorMessage: 'Build failed' }; |
There was a problem hiding this comment.
Nit: not super sure about the default. If there is ever a new state, it might fall back to FAILED even if it is not.
| const buildStatus = build.status as string; | ||
| const outcome = resolvePushOutcome(buildStatus); | ||
|
|
||
| const actorUrl = `https://console.apify.com${redirectUrlPart}/actors/${build.actId}`; |
There was a problem hiding this comment.
Nit: it is alright now, but I noticed we are hardcoding apify domain (api, console, ...) a lot in the cli. At some point we should develop a registry of domains so that we have a single source of truth.
Ignore as it is out of scope of this PR.
…son-output-for-agents2
| } else { | ||
| error({ message: 'Build failed!' }); | ||
| process.exitCode = CommandExitCodes.BuildFailed; | ||
| const lines = [ |
There was a problem hiding this comment.
Q: do we already implement same style logging in case the upload itself fails?
DaveHanns
left a comment
There was a problem hiding this comment.
Few nits and suggestions. Otherwise looks good.
Covers the status-mapping contract introduced by the new push status block / JSON output
FAILED, and the unknown-status fallthrough. Plus an assertion that errorMessage is present on failures and absent on success/running.
Closes #1136